/*********************************************************************
*
* Experiment1:  200 neurons on 200 x 200 space
*
*********************************************************************/

import java.util.LinkedList ;
import java.util.Random ;
import java.awt.Color ;

public class Experiment1 extends ParameterFramework implements INeuralNetExperiment {
	private INeuron baseNeuron = null ;
	private ISynapse baseSynapse = null ;
	private INeuron [ ] n = null ;
	private int NUM_NEURONS = 200 ;
	private double alpha = 1 , k = .05 ;
	private Random r = new Random ( ) ;
	private IAppliedCurrent Iapp ;
	private int runTime = 70000 , synapseGrowRate = 10000 ;
	private double gridSize = 200 , initialMeanV = .4 ;
	private int numBiased = 200 ;
	private double sizeNeuron = 1 ;
	
	public Experiment1 ( IAppliedCurrent Iapp ) {
		init ( Iapp ) ;
	}
	
	public Experiment1 ( ) {
		init ( new ConstantCurrent ( 0 ) ) ;
	}
	
	private void init ( IAppliedCurrent Iapp ) {
		String [ ] params = { "alpha" , "k" , "numNeurons" , "runTime" , "synapseGrowRate" ,
		                      "gridSize" , "initialMeanV" , "numBiased" , "sizeNeuron" } ;
		this.Iapp = Iapp ;
		parameterNames = params ;	
	}
	
	
	public void setCurrent ( IAppliedCurrent Iapp ) {
		this.Iapp = Iapp ;
	}
	
	public String getName ( ) {
		return "Experiment 1" ;
	}	
	
	public String getParam ( String name ) {
		if ( name.equals ( "numNeurons" ) ) {
			return "" + NUM_NEURONS ;
		} else if ( name.equals ( "sizeNeuron" ) ) {
			return "" + sizeNeuron ;
		} else if ( name.equals ( "numBiased" ) ) {
			return "" + numBiased ;
		} else if ( name.equals ( "runTime" ) ) {
			return "" + runTime ;
		} else if ( name.equals ( "alpha" ) ) {
			return "" + alpha ;
		} else if ( name.equals ( "k" ) ) {
			return "" + k ;
		} else if ( name.equals ( "synapseGrowRate" ) ) {
			return "" + synapseGrowRate ;
		} else if ( name.equals ( "gridSize" ) ) {
			return "" + gridSize ;
		} else if ( name.equals ( "initialMeanV" ) ) {
			return "" + initialMeanV ;
		} else {
			return null ;
		}
	}
	
	public void setParam ( String name , String data ) {
		double value ;
		
		// all parameters are doubles or ints
		try {
			value = Double.parseDouble ( data ) ;
		} catch ( NumberFormatException e ) {
			// not a double; ignore the set request
			return ;
		}
		if ( name.equals ( "numNeurons" ) ) {
			NUM_NEURONS = ( int ) Math.round ( value ) ;
		} else if ( name.equals ( "sizeNeuron" ) ) {
			sizeNeuron = value ;
		} else if ( name.equals ( "numBiased" ) ) {
			numBiased = ( int ) Math.round ( value ) ;
		} else if ( name.equals ( "runTime" ) ) {
			runTime = ( int ) Math.round ( value ) ;
		} else if ( name.equals ( "alpha" ) ) {
			alpha = value ;
		} else if ( name.equals ( "k" ) ) {
			k = value ;
		} else if ( name.equals ( "synapseGrowRate" ) ) {
			synapseGrowRate = ( int ) Math.round ( value ) ;
		} else if ( name.equals ( "gridSize" ) ) {
			gridSize = value ;
		} else if ( name.equals ( "initialMeanV" ) ) {
			initialMeanV = value ;
		}
	}
	
	public INeuralNetExperiment clone ( ) {
		Experiment1 c = new Experiment1 ( Iapp ) ;
		c.setParam ( getParams ( ) ) ;
		return c ;
	}

	
	public LinkedList<INeuron> getNeurons ( ) {
		LinkedList<INeuron> result = new LinkedList<INeuron> ( ) ;
		int i ;
		for ( i = 0 ; i < n.length ; i ++ ) {
			result.add ( n [ i ] ) ;
		}
		return result ;
	}
	
	public void init ( INeuron n , ISynapse s ) {
		baseNeuron = n ;
		baseSynapse = s ;
	}
	
	private void growSynapses ( ) {
		int i , j ;
		int count = 0 ;
		double denom ;
		for ( i = 0 ; i < NUM_NEURONS ; i ++ ) {
			for ( j = 0 ; j < NUM_NEURONS ; j ++ ) {
				if ( i != j ) {
					denom = Math.max ( .000001 , n[j].dist ( n[i] ) - sizeNeuron ) ;
					if ( r.nextDouble ( ) < k / denom ) {
						// grow a synapse (store on the post-synaptic neuron)
						n[j].addSynapse ( baseSynapse.clone ( n[i] , n[j] , r.nextGaussian ( ) * Math.sqrt( .04 ) ) ) ;
						count ++ ;
					}
				}
			}
		}
		System.out.println ( "Just added " + count + " synapses." ) ;
	}
	
	public void run ( ) {
		int i , j ;
		
		if ( baseNeuron == null ) return ;

		// clone the neurons, position them, etc...
		n = new INeuron [ NUM_NEURONS ] ;
		for ( j = 0 ; j < NUM_NEURONS ; j ++ ) {
			n [ j ] = baseNeuron.clone ( ) ;
			n[j].setTime ( 0 ) ;
			n[j].setPotential ( r.nextGaussian ( ) * Math.sqrt ( .3 ) + initialMeanV ) ;
			n[j].setPlace ( new Point ( r.nextDouble ( ) * gridSize , r.nextDouble ( ) * gridSize ) ) ;
			if ( j < numBiased ) {
				n[j].setAppliedCurrent ( Iapp ) ;
			}
		}
		

		// run
		for ( i = 0 ; i < runTime ; i ++ ) {
			if ( ( i % synapseGrowRate ) == 0 ) {
				growSynapses ( ) ;
			}
			for ( j = 0 ; j < NUM_NEURONS ; j ++ ) {
				n[j].advance ( ) ;
			}
			for ( j = 0 ; j < NUM_NEURONS ; j ++ ) {
				n[j].advanceSynapses ( ) ;
			}
		}
	}
	
	
	private Curve appliedCurrentCurve ( int tStart , int tStop ) {
		int i ;
		OrderedPair [ ] pts = new OrderedPair [ tStop - tStart ] ;
		Curve result ;
		
		for ( i = 0 ; i < tStop - tStart ; i ++ ) {
			pts [ i ] = new OrderedPair ( i + tStart , Iapp.amount ( i + tStart ) ) ;
		}
		result = new Curve ( pts , "Iapp" ) ;
		result.setColor ( Color.BLUE ) ;
		return result ;
	}

	public LinkedList<Curve> getCurves ( ) {
		return getCurves ( n[0].getTime ( ) - 500 , n[0].getTime ( ) ) ;
	}
		
	public LinkedList<Curve> getCurves ( int tStart , int tStop ) {
		LinkedList<Curve> result = new LinkedList<Curve> ( ) ;
		int j , t , t2 ;
		Curve c ;
		boolean hasFired ;
		
		if ( n == null ) return result ;
		
		// add a curve for the applied current
		result.add ( appliedCurrentCurve ( tStart , tStop ) ) ;

		
		// loop through the neurons, add each curve... but only
		// the ones that have fired an action potential in the interval in question
		t = n[0].getTime ( ) ;
		for ( j = 0 ; j < NUM_NEURONS ; j ++ ) {
			hasFired = false ;
			for ( t2 = tStart ; t2 < tStop ; t2 ++ ) {
				if ( n[j].getPotential ( t2 ) >= .5 ) {
					hasFired = true ;
					break ;
				}
			}
			if ( hasFired ) {
				c = n[j].getCurve ( tStart , tStop ) ;
				c.id = "" + ( j + 1 ) ;
				result.add ( c ) ;
			}
		}
		
		return result ;
	}
		
}
